/* -------------------------------------------------------------------
 *    Application class for RubiksCube
 *
 *    $File: RubiksCubeModel.cpp
 *    $Date: 2013/01/31 $
 *    $Revision: #1 $
 * -------------------------------------------------------------------*/
#include "RubiksCubeModel.h"

// C'tor
RubiksCubeModel::RubiksCubeModel(void) : explosionGrade(1.0f) {
	resetCube();
}

void RubiksCubeModel::resetCube(void){
	for (int x = 0; x < 3; ++x) {  // dim X
		for (int y = 0; y < 3; ++y) {  // dim Y
			for (int z = 0; z < 3; ++z) { // dim Z
				int* sidecolors = cubies[x][y][z].getSidecolors();
				switch (x) {  // colorize on X-axis
				case 0: 
					sidecolors[LEFT] = YELLOW;
					sidecolors[RIGHT] = BLACK;
					break;
				case 1: 					
					sidecolors[LEFT] = BLACK;
					sidecolors[RIGHT] = BLACK;
					break;
				case 2: 		
					sidecolors[LEFT] = BLACK;
					sidecolors[RIGHT] = WHITE;
					break;
				}
				switch (y) {  // colorize on Y-axis
				case 0: 
					sidecolors[DOWN] = ORANGE;
					sidecolors[UPPER] = BLACK;
					break;
				case 1: 					
					sidecolors[DOWN] = BLACK;
					sidecolors[UPPER] = BLACK;
					break;
				case 2: 		
					sidecolors[DOWN] = BLACK;
					sidecolors[UPPER] = RED;
					break;
				}
				switch (z) {  // colorize on Z-axis
				case 0: 
					sidecolors[BACK] = GREEN;
					sidecolors[FRONT] = BLACK;
					break;
				case 1: 					
					sidecolors[BACK] = BLACK;
					sidecolors[FRONT] = BLACK;
					break;
				case 2: 		
					sidecolors[BACK] = BLACK;
					sidecolors[FRONT] = BLUE;
					break;
				}
				cubies[x][y][z].setSidecolors(sidecolors);
				cubies[x][y][z].resetOrientation();
			}
		}
	}
}

void RubiksCubeModel::swapCubies(int participants[][3], Rotation rot) {
	Cuby tmp;
	switch (rot) {
	case CLOCKWISE:
		// move corners
		tmp = cubies[participants[0][0]][participants[0][1]][participants[0][2]];
		for (int i = 0 ; i < 3 ; ++i ) {
			cubies[participants[i][0]][participants[i][1]][participants[i][2]] 
				= cubies[participants[i+1][0]][participants[i+1][1]][participants[i+1][2]];
		}
		cubies[participants[3][0]][participants[3][1]][participants[3][2]] = tmp;
		
		// move edges
		tmp = cubies[participants[4][0]][participants[4][1]][participants[4][2]];
		for (int i = 4 ; i < 7 ; ++i ) {
			cubies[participants[i][0]][participants[i][1]][participants[i][2]] 
				= cubies[participants[i+1][0]][participants[i+1][1]][participants[i+1][2]];
		}
		cubies[participants[7][0]][participants[7][1]][participants[7][2]] = tmp;
		break;
	case COUNTERCLOCKWISE:
		// move corners
		tmp = cubies[participants[3][0]][participants[3][1]][participants[3][2]];
		for (int i = 3 ; i > 0 ; --i ) {
			cubies[participants[i][0]][participants[i][1]][participants[i][2]] 
				= cubies[participants[i-1][0]][participants[i-1][1]][participants[i-1][2]];
		}
		cubies[participants[0][0]][participants[0][1]][participants[0][2]] = tmp;

		// move edges
		tmp = cubies[participants[7][0]][participants[7][1]][participants[7][2]];
		for (int i = 7 ; i > 4 ; --i ) {
			cubies[participants[i][0]][participants[i][1]][participants[i][2]] 
				= cubies[participants[i-1][0]][participants[i-1][1]][participants[i-1][2]];
		}
		cubies[participants[4][0]][participants[4][1]][participants[4][2]] = tmp;
		break;
	}
}

void RubiksCubeModel::rotateRightSlice(Rotation rot)
{
	// first four entries are the corners, the second four are the edges
	int participants[8][3] = {{2,0,2},{2,0,0},{2,2,0},{2,2,2},{2,0,1},{2,1,0},{2,2,1},{2,1,2}};
	swapCubies(participants, rot);
	// adjust orientation
	for (int y = 0 ; y < 3 ; ++y) {
		for (int z = 0 ; z < 3 ; ++z) {
			cubies[2][y][z].rotateX(rot);
		}
	}
	notify();
}

void RubiksCubeModel::rotateMiddleSlice(Rotation rot) 
{
	// first four entries are the centers, the second four are the edges
	int participants[8][3] = {{1,2,2},{1,0,2},{1,0,0},{1,2,0},{1,2,1},{1,1,2},{1,0,1},{1,1,0}};
	swapCubies(participants, rot);
	// adjust orientation
	for (int y = 0 ; y < 3 ; ++y) {
		for (int z = 0 ; z < 3 ; ++z) {
			cubies[1][y][z].rotateX(rot);
		}
	}
	notify();
}

void RubiksCubeModel::rotateLeftSlice(Rotation rot)
{
	// first four entries are the corners, the second four are the edges
	int participants[8][3] = {{0,2,0},{0,2,2},{0,0,2},{0,0,0},{0,1,0},{0,2,1},{0,1,2},{0,0,1}};
	swapCubies(participants, rot);
	// adjust orientation
	for (int y = 0 ; y < 3 ; ++y) {
		for (int z = 0 ; z < 3 ; ++z) {
			cubies[0][y][z].rotateX(rot);
		}
	}
	notify();
}

void RubiksCubeModel::rotateUpperSlice(Rotation rot)
{
	// first four entries are the corners, the second four are the edges
	int participants[8][3] = {{2,2,0},{0,2,0},{0,2,2},{2,2,2},{2,2,1},{1,2,0},{0,2,1},{1,2,2}};
	swapCubies(participants, rot);
	// adjust orientation
	for (int x = 0 ; x < 3 ; ++x) {
		for (int z = 0 ; z < 3 ; ++z) {
			cubies[x][2][z].rotateY(rot);
		}
	}
	notify();
}

void RubiksCubeModel::rotateEquatorSlice(Rotation rot)
{
	// first four entries are the centers, the second four are the edges
	int participants[8][3] = {{0,1,0},{0,1,2},{2,1,2},{2,1,0},{0,1,1},{1,1,2},{2,1,1},{1,1,0}};
	swapCubies(participants, rot);
	// adjust orientation
	for (int x = 0 ; x < 3 ; ++x) {
		for (int z = 0 ; z < 3 ; ++z) {
			cubies[x][1][z].rotateY(rot);
		}
	}
	notify();
}

void RubiksCubeModel::rotateDownSlice(Rotation rot)
{
	// first four entries are the corners, the second four are the edges
	int participants[8][3] = {{0,0,2},{2,0,2},{2,0,0},{0,0,0},{1,0,2},{2,0,1},{1,0,0},{0,0,1}};
	swapCubies(participants, rot);
	// adjust orientation
	for (int x = 0 ; x < 3 ; ++x) {
		for (int z = 0 ; z < 3 ; ++z) {
			cubies[x][0][z].rotateY(rot);
		}
	}
	notify();
}

void RubiksCubeModel::rotateFrontSlice(Rotation rot)
{
	// first four entries are the corners, the second four are the edges
	int participants[8][3] = {{0,2,2},{0,0,2},{2,0,2},{2,2,2},{0,1,2},{1,0,2},{2,1,2},{1,2,2}};
	swapCubies(participants, rot);
	// adjust orientation
	for (int x = 0 ; x < 3 ; ++x) {
		for (int y = 0 ; y < 3 ; ++y) {
			cubies[x][y][2].rotateZ(rot);
		}
	}
	notify();
}

void RubiksCubeModel::rotateStandingSlice(Rotation rot)
{
	// first four entries are the centers, the second four are the edges
	int participants[8][3] = {{2,0,1},{2,2,1},{0,2,1},{0,0,1},{1,0,1},{2,1,1},{1,2,1},{0,1,1}};
	swapCubies(participants, rot);
	// adjust orientation
	for (int x = 0 ; x < 3 ; ++x) {
		for (int y = 0 ; y < 3 ; ++y) {
			cubies[x][y][1].rotateZ(rot);
		}
	}
	notify();
}

void RubiksCubeModel::rotateBackSlice(Rotation rot)
{
	// first four entries are the corners, the second four are the edges
	int participants[8][3] = {{2,0,0},{2,2,0},{0,2,0},{0,0,0},{0,1,0},{1,0,0},{2,1,0},{1,2,0}};
	swapCubies(participants, rot);
	// adjust orientation
	for (int x = 0 ; x < 3 ; ++x) {
		for (int y = 0 ; y < 3 ; ++y) {
			cubies[x][y][0].rotateZ(rot);
		}
	}
	notify();
}

void RubiksCubeModel::rotateCubeXAxis(Rotation rot)
{
	rotateRightSlice(rot);
	rotateMiddleSlice(rot);
	rotateLeftSlice(rot);
	notify();
}

void RubiksCubeModel::rotateCubeYAxis(Rotation rot)
{
	rotateUpperSlice(rot);
	rotateEquatorSlice(rot);
	rotateDownSlice(rot);
	notify();
}

void RubiksCubeModel::rotateCubeZAxis(Rotation rot)
{
	rotateFrontSlice(rot);
	rotateStandingSlice(rot);
	rotateBackSlice(rot);
	notify();
}

// inc/dec explosion grade between 1 and 2 by 0.1 
void RubiksCubeModel::explode(bool increase)
{
	if (increase && explosionGrade < 2.0f)
		explosionGrade += 0.1;
	else if (!increase && explosionGrade > 1.0f)
		explosionGrade -= 0.1;
	notify();
}

// do n-times random operation on the model
void RubiksCubeModel::shuffleCube(int times)
{
	unsigned int random;

	for (int i = 0 ; i < times ; ++i)
	{
		random = std::rand() % 24;
		switch(random) {
			case 0: rotateRightSlice(CLOCKWISE); break;
			case 1: rotateRightSlice(COUNTERCLOCKWISE); break;
			case 2: rotateMiddleSlice(CLOCKWISE); break;
			case 3: rotateMiddleSlice(COUNTERCLOCKWISE); break;
			case 4: rotateLeftSlice(COUNTERCLOCKWISE); break;
			case 5: rotateLeftSlice(CLOCKWISE); break;
			case 6: rotateUpperSlice(CLOCKWISE); break;
			case 7: rotateUpperSlice(COUNTERCLOCKWISE); break;
			case 8: rotateEquatorSlice(CLOCKWISE); break;
			case 9: rotateEquatorSlice(COUNTERCLOCKWISE); break;
			case 10: rotateDownSlice(COUNTERCLOCKWISE); break;
			case 11: rotateDownSlice(CLOCKWISE); break;
			case 12: rotateFrontSlice(CLOCKWISE); break;
			case 13: rotateFrontSlice(COUNTERCLOCKWISE); break;
			case 14: rotateStandingSlice(CLOCKWISE); break;
			case 15: rotateStandingSlice(COUNTERCLOCKWISE); break;
			case 16: rotateBackSlice(COUNTERCLOCKWISE); break;
			case 17: rotateBackSlice(CLOCKWISE); break;
			case 18: rotateCubeXAxis(CLOCKWISE); break;
			case 19: rotateCubeXAxis(COUNTERCLOCKWISE); break;
			case 20: rotateCubeYAxis(CLOCKWISE); break;
			case 21: rotateCubeYAxis(COUNTERCLOCKWISE); break;
			case 22: rotateCubeZAxis(CLOCKWISE); break;
			case 23: rotateCubeZAxis(COUNTERCLOCKWISE); break;
			default: break;		 
		}
	}
}

float RubiksCubeModel::getExplosionGrade() const {
	return explosionGrade;
}